package org.JWCB.friend.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.PageHelper;
import org.JWCB.common.core.constants.CacheConstants;
import org.JWCB.common.core.enums.ExamListType;
import org.JWCB.common.core.enums.ExamStatus;
import org.JWCB.common.core.enums.ResultCode;
import org.JWCB.common.core.exception.ServiceException;
import org.JWCB.common.redis.service.RedisService;
import org.JWCB.friend.domain.exam.dto.ExamQueryDto;
import org.JWCB.friend.domain.exam.entity.Exam;
import org.JWCB.friend.domain.exam.entity.ExamQuestion;
import org.JWCB.friend.domain.exam.vo.ExamListItemVo;
import org.JWCB.friend.domain.exam.vo.ExamRankListItemVo;
import org.JWCB.friend.domain.user.dto.UserScore;
import org.JWCB.friend.domain.user.entity.UserExam;
import org.JWCB.friend.domain.user.vo.UserDetailVo;
import org.JWCB.friend.mapper.exam.ExamMapper;
import org.JWCB.friend.mapper.exam.ExamQuestionMapper;
import org.JWCB.friend.mapper.user.UserExamMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class ExamCacheManager {

    private static final Logger log = LoggerFactory.getLogger(ExamCacheManager.class);
    @Autowired
    private ExamMapper examMapper;


    @Autowired
    private RedisService redisService;
    @Autowired
    private UserExamMapper userExamMapper;


    @Autowired
    private ExamQuestionMapper examQuestionMapper;


    @Autowired
    private UserCacheManager userCacheManager;
    /**
     *
     * @param examListType 0--未完赛竞赛 1-- 历史竞赛
     * @return 得到redis里面的examList长度
     */
    public Long getListSize(Integer examListType,Long userId) {
        String examListKey = getExamListKey(examListType,userId);
        return redisService.getListSize(examListKey);
    }


    /**
     * 获取题目中竞赛列表的数量
     * @param examId
     * @return
     */
    public Long getExamQuestionListSize(Long examId) {
        String examQuestionListKey = getExamQuestionListKey(examId);
        return redisService.getListSize(examQuestionListKey);
    }

    public List<ExamListItemVo> getExamList(ExamQueryDto examQueryDto,Long userId) {
        int start = (examQueryDto.getPageNum()-1) * examQueryDto.getPageSize();
        int end = start + examQueryDto.getPageSize() - 1; // 下标-1
        String examListKey = getExamListKey(examQueryDto.getType(),userId);
        List<Long> examIdList = redisService.getCacheListByRange(examListKey,start,end,Long.class);
        List<ExamListItemVo> examListItemVos = assembleExamItemVoList(examIdList);
        if(CollectionUtil.isEmpty(examListItemVos)) {
            // 说明redis中的数据可能存在问题,需要重新刷新缓存
            examListItemVos = getExamListByDB(examQueryDto,userId);
            refreshCache(examQueryDto.getType(),userId);
        }
        return examListItemVos;
    }


    /**
     * 插入我的竞赛列表
     */
    public void addUserExamCache(Long userId,Long examId) {
        String userExamKey = getUserExamKey(userId);
        redisService.leftPushForList(userExamKey,examId);
    }
    /**
     * 从数据库重新获取数据
     * @param type 数据类型 0--未完赛 1-- 历史竞赛
     */
    public void refreshCache(Integer type,Long userId) {
        List<Exam> examList = new ArrayList<>();
        if(ExamListType.EXAM_UN_FINISHED_LIST.getValue().equals(type)) {
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .select(Exam::getExamId,Exam::getTitle,Exam::getStartTime,Exam::getEndTime)
                    .gt(Exam::getEndTime, LocalDateTime.now())
                    .eq(Exam::getStatus, ExamStatus.PUBLISHED.getCode())
                    .orderByDesc(Exam::getStartTime));
        }else if(ExamListType.EXAM_HISTORY_LIST.getValue().equals(type)) {
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .select(Exam::getExamId,Exam::getTitle,Exam::getStartTime,Exam::getEndTime)
                    .le(Exam::getEndTime, LocalDateTime.now())
                    .eq(Exam::getStatus, ExamStatus.PUBLISHED.getCode())
                    .orderByDesc(Exam::getStartTime));
        }else if(ExamListType.USER_EXAM_LIST.getValue().equals(type)) {
            List<ExamListItemVo> examListItemVos = userExamMapper.selectUserExamList(userId);
            examList = BeanUtil.copyToList(examListItemVos,Exam.class);
        }
        if(CollectionUtil.isEmpty(examList)) {
            return;
        }
        // 放入缓存
        Map<String,Exam> examMap = new HashMap<>();
        List<Long> examIdList = new ArrayList<>();
        for(Exam exam : examList) {
            examMap.put(getDetailKey(exam.getExamId()),exam);
            examIdList.add(exam.getExamId());
        }
        redisService.multiSet(examMap);
        redisService.deleteObject(getExamListKey(type,userId));
        redisService.rightPushAll(getExamListKey(type,userId),examIdList);
    }


    /**
     * 刷新竞赛题目列表数据
     * @param examId 竞赛id
     */
    public void refreshExamQuestionListCache(Long examId) {
        List<ExamQuestion> examQuestionIds = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .select(ExamQuestion::getQuestionId)
                .orderByAsc(ExamQuestion::getQuestionOrder));
        if(CollectionUtil.isEmpty(examQuestionIds)) {
            log.error("竞赛题目为空!!!!");
            return;
        }
        List<Long> idList = examQuestionIds.stream().map(ExamQuestion::getQuestionId).toList();
        redisService.rightPushAll(getExamQuestionListKey(examId),idList);
        long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
        redisService.expire(getExamQuestionListKey(examId),seconds, TimeUnit.SECONDS);
    }
    /**
     * 从数据库中获取数据
     * @param examQueryDto 查询信息
     * @return 数据
     */
    private List<ExamListItemVo> getExamListByDB(ExamQueryDto examQueryDto,Long userId) {
        PageHelper.startPage(examQueryDto.getPageNum(),examQueryDto.getPageSize());
        if(ExamListType.USER_EXAM_LIST.getValue().equals(examQueryDto.getType())) {
            // 查询的是我的竞赛列表
            return userExamMapper.selectUserExamList(userId);
        }else {
            return examMapper.selectExamList(examQueryDto);
        }
    }

    /**
     * @param examIdList examIds
     * @return 根据examIds得到examList
     */
    private List<ExamListItemVo> assembleExamItemVoList(List<Long> examIdList) {
        if(CollectionUtil.isEmpty(examIdList)) {
            return null;
        }
        List<String> detailKeyList = new ArrayList<>();
        for(Long examId : examIdList) {
            detailKeyList.add(getDetailKey(examId));
        }
        List<ExamListItemVo> examListItemVos = redisService.multiGet(detailKeyList,ExamListItemVo.class);
        CollUtil.removeNull(examListItemVos);
        if(CollectionUtil.isEmpty(examListItemVos) || examListItemVos.size() != examIdList.size()) {
            return null;
        }
        return examListItemVos;
    }

    /**
     * @param examId 竞赛id
     * @return 根据竞赛id获取存储在缓存里面的竞赛详情key
     */
    private String getDetailKey(Long examId) {
        return CacheConstants.EXAM_DETAIL_KEY + examId;
    }

    /**
     * 根据类别得到缓存中的竞赛key
     * @param examListType 0--未完赛竞赛 1-- 历史竞赛
     * @return key
     */
    private String getExamListKey(Integer examListType,Long userId) {
        if(ExamListType.EXAM_UN_FINISHED_LIST.getValue().equals(examListType)) {
            return CacheConstants.EXAM_UNFINISHED_LIST_KEY;
        }else if(ExamListType.EXAM_HISTORY_LIST.getValue().equals(examListType)) {
            return CacheConstants.EXAM_HISTORY_LIST_KEY;
        }else {
            return CacheConstants.USER_EXAM_LIST_KEY+userId;
        }
    }


    /**
     * 得到缓存里面存的我的竞赛列表的key
     */
    private String getUserExamKey(Long userId) {
        return CacheConstants.USER_EXAM_LIST_KEY + userId;
    }


    /**
     * 得到用户的所有已经报名的竞赛ids
     * @param userId 用户id
     */
    public List<Long> getAllUserExam(Long userId) {
        String examListKey = getExamListKey(ExamListType.USER_EXAM_LIST.getValue(), userId);
        List<Long> userExamIds = redisService.getCacheListByRange(examListKey,0,-1,Long.class);
        if(CollectionUtil.isNotEmpty(userExamIds)) {
            return userExamIds;
        }
        List<UserExam> userExams = userExamMapper.selectList(new LambdaQueryWrapper<UserExam>()
                .eq(UserExam::getUserId, userId));
        if(CollectionUtil.isEmpty(userExams)) {
            return new ArrayList<>();
        }
        refreshCache(ExamListType.USER_EXAM_LIST.getValue(), userId);
        return userExams.stream().map(UserExam::getExamId).collect(Collectors.toList());
    }


    /**
     * 得到竞赛中题目的列表
     */
    public Long getFirstQuestion(long examId) {
        return redisService.indexForList(getExamQuestionListKey(examId),0,Long.class);
    }
    private String getExamQuestionListKey(Long examId) {
        return CacheConstants.EXAM_QUESTION_LIST_KEY + examId;
    }


    /**
     * 得到竞赛
     */
    public Exam getExam(long examId){
        return redisService.getCacheObject(getDetailKey(examId),Exam.class);
    }


    /**
     * 获取竞赛下一道题
     */
    public Long getNextQuestionId(Long examId,Long questionID) {
        long listSize = getExamQuestionListSize(examId);
        if(listSize <= 0) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        String examQuestionListKey = getExamQuestionListKey(examId);
        long index = redisService.indexOfForList(examQuestionListKey,questionID);
        if(index < 0) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        if(index == listSize-1) {
            throw new ServiceException(ResultCode.LAST_QUESTION);
        }
        return redisService.indexForList(examQuestionListKey,index+1,Long.class);
    }


    /**
     * 获取下一道题
     */
    public Long getPrevQuestionId(Long examId,Long questionID) {
        String examQuestionListKey = getExamQuestionListKey(examId);
        long index = redisService.indexOfForList(examQuestionListKey,questionID);
        if(index < 0) {
            throw new ServiceException(ResultCode.FAILED_NOT_EXISTS);
        }
        if(index == 0) {
            throw new ServiceException(ResultCode.FIRST_QUESTION);
        }
        return redisService.indexForList(getExamQuestionListKey(examId),index-1,Long.class);
    }


    /**
     * 获取缓存中竞赛排名信息
     * @param examId 竞赛id
     * @return 排名
     */
    public List<ExamRankListItemVo> getExamRankList(Long examId, int pageSize, int pageNum) {
        int start = (pageNum-1) * pageSize;
        int end = start + pageSize - 1;
        List<UserScore> examScoreList = redisService.getCacheListByRange(getExamRankListKey(examId), start, end, UserScore.class);
        if(examScoreList != null && examScoreList.size() == 1 && examScoreList.get(0).getExamId() == -1) {
            return new ArrayList<>();
        }
        if(CollectionUtil.isEmpty(examScoreList)) {
            examScoreList = getExamRankListDB(examId,pageSize,pageNum);
            refreshExamRankList(examId);
        }

        return assembleExamRankList(examScoreList);
    }



    /**
     * 拼装examScoreList返回结果
     */
    private List<ExamRankListItemVo> assembleExamRankList(List<UserScore> examScoreList) {
        List<ExamRankListItemVo> examRankListItemVoList = new ArrayList<>();
        for (UserScore userScore : examScoreList) {
            UserDetailVo userDetailVo = userCacheManager.getUserInfo(userScore.getUserId());
            if(userDetailVo != null) {
                examRankListItemVoList.add(assembleRankListVo(userDetailVo,userScore));
            }
        }
        return examRankListItemVoList;
    }


    /**
     * 组装单个examScoreList
     */
    private ExamRankListItemVo assembleRankListVo(UserDetailVo userDetailVo, UserScore userScore) {
        ExamRankListItemVo rankListItemVo = new ExamRankListItemVo();
        rankListItemVo.setExamRank(userScore.getExamRank());
        rankListItemVo.setHeadImage(userDetailVo.getHeadImage());
        rankListItemVo.setNickName(userDetailVo.getNickName());
        rankListItemVo.setScore(userScore.getScore());
        return rankListItemVo;
    }


    /**
     * 获取竞赛排名列表的长度
     */
    public long getExamRankListSize(long examId) {
        String key = getExamRankListKey(examId);
        return redisService.getListSize(getExamRankListKey(examId));
    }
    /**
     * 获取竞赛排名缓存key
     */
    private String getExamRankListKey(Long examId) {
        return CacheConstants.EXAM_RANK_LIST_KEY + examId;
    }


    /**
     * 刷新竞赛排名缓存
     */
    public void refreshExamRankList(Long examId) {
        String key = getExamRankListKey(examId);
        List<UserExam> userExams = userExamMapper.selectList(new LambdaQueryWrapper<UserExam>()
                .eq(UserExam::getExamId, examId)
                .isNotNull(UserExam::getScore)
                .isNotNull(UserExam::getExamRank)
                .orderByAsc(UserExam::getExamRank));
        List<UserScore> userScoreList = BeanUtil.copyToList(userExams, UserScore.class);
        if(CollectionUtil.isEmpty(userScoreList)) {
            // 防止缓存穿透,设置空值
            userScoreList.add(getEmptyUserScore());
            redisService.rightPushAll(key,userScoreList);
            redisService.expire(key,CacheConstants.EXAM_RANK_LIST_NONE_EXP,TimeUnit.MINUTES);
        }else {
            redisService.rightPushAll(key,userScoreList);
        }
    }


    public UserScore getEmptyUserScore() {
        UserScore userScore = new UserScore();
        userScore.setUserId(-1L);
        userScore.setExamId(-1L);
        userScore.setScore(-1);
        userScore.setExamRank(-1);
        return userScore;
    }

    /**
     * 从数据库获取竞赛排名数据
     */
    private List<UserScore> getExamRankListDB(long examId,int pageSize,int pageNum) {
        PageHelper.startPage(pageNum,pageSize);
        List<UserExam> userExams = userExamMapper.selectList(new LambdaQueryWrapper<UserExam>()
                .eq(UserExam::getExamId, examId)
                .isNotNull(UserExam::getScore)
                .isNotNull(UserExam::getExamRank)
                .orderByAsc(UserExam::getExamRank));
        return BeanUtil.copyToList(userExams, UserScore.class);
    }

}
